package resolution;

import programme.Programme;
import manager.Manager;
import manager.Solution;

/**
 * Cette classe permet d'implémenter l'algorithme de recherche de VNS.
 * Pour cela, elle possède un certain nombre d'attributs static, permettant de configurer l'exécution de VNS.
 * Elle possède aussi une fonction static recherchant la borne primale d'un problème
 * qui applique VNS ainsi qu'une fonction pour chercher un optimum local autour d'une solution.
 * 
 * @author BERNARD Arthur, DELAMARCHE Jérôme, PAVAGEAU Max
 *
 */
public class VNS {
	/**
	 * nbVisitMax est la profondeur maximale de voisinage etudiée.
	 */
	public static int nbVisitMax = 10;
	
	/**
	 * eps correspond à la valeur d'epsilon, pour la condition d'arrêt de VNS.
	 */
	public static double eps = 20;
	
	/**
	 * nbIterMax est le nombre maximum d'itérations sans améliorations de la solution pour sortir de VNS.
	 */
	public static int nbIterMax = 20000;
	
	/**
	 * Référence vers le programme à optimiser, permettant d'accéder à toutes les données nécessaires.
	 */
	public static Programme programme;
	
	/**
	 * L'interface graphique dans laquelle devront être écrites les sorties textuelles.
	 */
	public static Manager manager;
	
	/**
	 * Permet de rechercher une borne primale du problème en utilisant l'algorithme de VNS.
	 * 
	 * @param verbose Booléen indiquant si le mode verbose est activé ou non.
	 * @return La solution donnant la meilleure borne primale trouvée.
	 */
	public static Solution rechercherBornePrimale(boolean verbose, boolean log) {
		Solution solution = Solution.getSolutionInit(VNS.programme.getDonnees().taille);
		double meilleur = VNS.programme.fonctionObjectif(solution);
		double oldMeilleur = meilleur;
		boolean changed;
		int nbIter = 0;
		
		if(verbose || log) {
			manager.printOut("--Commencement de VNS--");
			manager.printOut("");
			manager.printOut("Solution initiale : " + solution);
			manager.printOut("");
			manager.printOut("Paramètres : nbIterMax = " + nbIterMax + ", nbVisitMax = " + nbVisitMax + ", epsilon = " + eps);
			manager.printOut("");
		}
		
		while(nbIter < nbIterMax) {
			int k = 1;
			changed = false;
			
			while(k <= nbVisitMax) {
				//-- 1)Shaking --
				Solution solution2 = solution.getVoisAlea(k);
				
				//-- 2)Local Search --
				Solution solution3 = rechercheLocale(solution2);
				
				//-- 3)Move Or Not --
				if(VNS.programme.fonctionObjectif(solution3) < meilleur) {
					oldMeilleur = meilleur;
					meilleur = VNS.programme.fonctionObjectif(solution3);
					solution = solution3;
					changed = true;
					k = 1;
					
					if(verbose || log) {
						manager.printOut("--Nouvelle solution--");
						manager.printOut("");
						manager.printOut("Solution : " + solution + "/Coût : " + meilleur);
						manager.printOut("");
					}
				}
				else
					k++;
			}
			
			if(!changed || oldMeilleur - meilleur < VNS.eps) {
				nbIter++;
			}
			else {
				nbIter = 0;
			}
		}
			
		return solution;
	}
	
	/**
	 * Permet de rechercher un optimum local dans le voisinage direct d'une solution donnée.
	 * 
	 * @param solutionTmp La solution dans le voisinage de laquelle on recherche l'optimum local.
	 * @return La solution donnant l'optimum local.
	 */
	private static Solution rechercheLocale(Solution solutionTmp) {
		Solution optimumLocal = solutionTmp;
		double meilleur = VNS.programme.fonctionObjectif(solutionTmp);
		
		for(int i = 1; i <= solutionTmp.getSolution().length; i++) { 
			for(int j = i + 1; j <= solutionTmp.getSolution().length; j++) {
				solutionTmp.permuterSites(i, j);
				
				if(VNS.programme.fonctionObjectif(solutionTmp) < meilleur) {
					meilleur = VNS.programme.fonctionObjectif(solutionTmp);
					optimumLocal = solutionTmp.clone();
				}
				
				solutionTmp.permuterSites(j, i); //On remet la solution comme "avant"
			}
		}
		
		return optimumLocal;
	}
}
